﻿
using Boilen.Primitives.Implementers;
using Boilen.Primitives.Members;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;


namespace Boilen.Primitives {

    public sealed partial class PartialType {

        /// <summary>
        /// Assigns the values used for the <see cref="IsSealed"/> property.
        /// </summary>
        public PartialType SetIsSealed( bool isSealed ) {
            this.IsSealed = isSealed;
            return this;
        }

        /// <summary>
        /// Assigns the values used for the <see cref="ConstructorAccessibility"/> property.
        /// </summary>
        public PartialType SetConstructorAccessibility( Accessibility constructorAccessibility ) {
            this.ConstructorAccessibility = constructorAccessibility;
            return this;
        }

        /// <summary>
        /// Adds a custom suppress message attribute to the accessor.
        /// </summary>
        public PartialType AddConstructorSuppressionAttribute( string category, string id, string justification, bool staticConstructor ) {
            var attributes = staticConstructor ? this.cctorAttributes_ : this.ctorAttributes_;
            attributes.Add( AttributeMember.SuppressMessage( category, id, justification ) );
            return this;
        }

        /// <summary>
        /// Assigns the values used for the <see cref="OverrideDefaultStyleKey"/> property.
        /// </summary>
        public PartialType SetOverrideDefaultStyleKey( bool overrideDefaultStyleKey ) {
            this.OverrideDefaultStyleKey = overrideDefaultStyleKey;
            return this;
        }

        /// <summary>
        /// Adds the namespace of a type to the collection of used namespaces.
        /// </summary>
        public PartialType AddNamespace( Type type ) {
            this.TypeRepository.AddNamespace( type );
            return this;
        }

        /// <summary>
        /// Adds a namespace to the collection of used namespaces.
        /// </summary>
        public PartialType AddNamespace( string nmspace ) {
            this.TypeRepository.AddNamespace( nmspace );
            return this;
        }

        /// <summary>
        /// Inserts an arbitrary piece of text at the specified location in the generated code.
        /// </summary>
        public PartialType InsertText( InsertionPoint location, string text ) {
            if( !this.insertedText_.ContainsKey( location ) )
                this.insertedText_.Add( location, "" );
            this.insertedText_[location] += text;
            return this;
        }


        /// <summary>
        /// Adds a new <see cref="ConstantProperty{T}"/> implementer to the partial type with the specified constant value.
        /// </summary>
        [Category( "Properties" )]
        [DisplayName( "Constant Property" )]
        [Description( ConstantProperty<object>.DescriptionFormat )]
        public PartialType AddConstantProperty<T>( string name, string description, T value, Action<ConstantProperty<T>> configure = null ) {
            return this.AddImplementer( new ConstantProperty<T>( this, name, description, value ), configure );
        }
        public PartialType AddConstantProperty<T>( string name, T value, Action<ConstantProperty<T>> configure = null ) {
            return this.AddConstantProperty<T>( name, null, value, configure );
        }

        /// <summary>
        /// Adds a new <see cref="ImmutableProperty{T}"/> implementer to the partial type.
        /// </summary>
        [Category( "Properties" )]
        [DisplayName( "Immutable Property" )]
        [Description( ImmutableProperty<object>.DescriptionFormat )]
        public PartialType AddImmutableProperty<T>( string name, string description, Action<ImmutableProperty<T>> configure = null ) {
            return this.AddImplementer( new ImmutableProperty<T>( this, name, description ), configure );
        }
        public PartialType AddImmutableProperty<T>( string name, Action<ImmutableProperty<T>> configure = null ) {
            return this.AddImmutableProperty<T>( name, null, configure );
        }

        /// <summary>
        /// Adds a new <see cref="LazyProperty{T}"/> implementer to the partial type.
        /// </summary>
        [Category( "Properties" )]
        [DisplayName( "Lazy Property" )]
        [Description( LazyProperty<object>.DescriptionFormat )]
        public PartialType AddLazyProperty<T>( string name, string description, string initializerExpression, Action<LazyProperty<T>> configure = null ) where T : class {
            return this.AddImplementer( new LazyProperty<T>( this, name, description, initializerExpression ), configure );
        }
        public PartialType AddLazyProperty<T>( string name, string initializerExpression, Action<LazyProperty<T>> configure = null ) where T : class {
            return this.AddLazyProperty<T>( name, null, initializerExpression, configure );
        }

        /// <summary>
        /// Adds a new <see cref="MutableProperty{T}"/> implementer to the partial type.
        /// </summary>
        [Category( "Properties" )]
        [DisplayName( "Mutable Property" )]
        [Description( MutableProperty<object>.DescriptionFormat )]
        public PartialType AddMutableProperty<T>( string name, string description, Action<MutableProperty<T>> configure = null ) {
            return this.AddImplementer( new MutableProperty<T>( this, name, description ), configure );
        }
        public PartialType AddMutableProperty<T>( string name, Action<MutableProperty<T>> configure = null ) {
            return this.AddMutableProperty<T>( name, null, configure );
        }

        /// <summary>
        /// Adds a new <see cref="NewDependencyProperty{T}"/> implementer to the partial type.
        /// </summary>
        [Category( "Properties" )]
        [DisplayName( "Dependency Property" )]
        [Description( "Gets [or sets] {0}." )]
        public PartialType AddDependencyProperty<T>( string name, string description, Action<NewDependencyProperty<T>> configure = null ) {
            return this.AddImplementer( new NewDependencyProperty<T>( this, name, description ), configure );
        }
        public PartialType AddDependencyProperty<T>( string name, Action<NewDependencyProperty<T>> configure = null ) {
            return this.AddDependencyProperty<T>( name, null, configure );
        }

        /// <summary>
        /// Adds a new <see cref="ExistingDependencyProperty{T}"/> implementer to the partial type.
        /// </summary>
        [Category( "Properties" )]
        [DisplayName( "Existing Dependency Property" )]
        public PartialType AddDependencyProperty<T>( DependencyProperty existingProperty, Action<ExistingDependencyProperty<T>> configure ) {
            return this.AddImplementer( new ExistingDependencyProperty<T>( this, existingProperty ), configure );
        }
        public PartialType AddDependencyProperty<T>( DependencyProperty existingProperty ) {
            Action<ExistingDependencyProperty<T>> configure = null;
            return this.AddDependencyProperty<T>( existingProperty, configure );
        }
        private PartialType AddDependencyProperty<T>( DependencyProperty existingProperty, Action<IImplementer> configure ) {
            Action<ExistingDependencyProperty<T>> strongConfigure = null;
            if( configure != null )
                strongConfigure = p => configure( p );

            return this.AddDependencyProperty<T>( existingProperty, strongConfigure );
        }
        public PartialType AddDependencyProperty( DependencyProperty existingProperty, Action<IImplementer> configure ) {
            // Find definition of generic "AddDependencyProperty<T>( DependencyProperty, Action<IImplementer> )" method, 
            //  to create appropriately typed ExistingDepndencyProperty object.
            var methodDefinition =
                (from m in this.GetType( ).GetMethods( BindingFlags.NonPublic | BindingFlags.Instance )
                 where m.IsGenericMethodDefinition
                    && m.Name == "AddDependencyProperty"
                 let parameters = m.GetParameters( )
                 where parameters.Length == 2
                    && parameters[0].ParameterType == typeof( DependencyProperty )
                    && parameters[1].ParameterType == typeof( Action<IImplementer> )
                 select m
                ).Single( );

            var method = methodDefinition.MakeGenericMethod( existingProperty.PropertyType );
            return (PartialType)method.Invoke( this, new object[] { existingProperty, configure } );
        }
        public PartialType AddDependencyProperty( DependencyProperty existingProperty ) { return this.AddDependencyProperty( existingProperty, null ); }

        /// <summary>
        /// Adds a new <see cref="MetadataOverride{T}"/> implementer to the partial type.
        /// </summary>
        [Category( "Framework" )]
        [DisplayName( "Metadata Override" )]
        public PartialType AddMetadataOverride<T>( DependencyProperty existingProperty, Action<MetadataOverride<T>> configure ) {
            return this.AddImplementer( new MetadataOverride<T>( this, existingProperty ), configure );
        }
        public PartialType AddMetadataOverride<T>( DependencyProperty existingProperty, T defaultValue ) {
            return this.AddImplementer( new MetadataOverride<T>( this, existingProperty ), o => o.SetDefaultValue( defaultValue ) );
        }

        /// <summary>
        /// Adds a new <see cref="TemplatePart{T}"/> implementer to the partial type.
        /// </summary>
        [Category( "Framework" )]
        [DisplayName( "Template Part" )]
        [Description( TemplatePart<object>.DescriptionFormat )]
        public PartialType AddTemplatePart<T>( string name, string description, Action<TemplatePart<T>> configure = null ) where T : class {
            return this.AddImplementer( new TemplatePart<T>( this, name, description ), configure );
        }
        public PartialType AddTemplatePart<T>( string name, Action<TemplatePart<T>> configure = null ) where T : class {
            return this.AddTemplatePart<T>( name, null, configure );
        }

        /// <summary>
        /// Adds a new <see cref="Event{T}"/> implementer to the partial type.
        /// </summary>
        [Category( "Events" )]
        [DisplayName( "Event" )]
        [Description( Event<object>.DescriptionFormat )]
        public PartialType AddEvent<T>( string name, string description, Action<Event<T>> configure = null ) {
            return this.AddImplementer( new Event<T>( this, name, description ), configure );
        }
        public PartialType AddEvent<T>( string name, Action<Event<T>> configure = null ) {
            return this.AddEvent<T>( name, null, configure );
        }


        /// <summary>
        /// Adds a new <see cref="NotifyPropertyChangedInterface"/> implementer to the partial type.
        /// </summary>
        [Category( "Interfaces" )]
        [DisplayName( "INotifyPropertyChanged" )]
        public PartialType ImplementINotifyPropertyChanged( Action<NotifyPropertyChangedInterface> configure = null ) {
            return this.AddImplementer( new NotifyPropertyChangedInterface( this ), configure );
        }

        /// <summary>
        /// Adds a new <see cref="EquatableInterface"/> implementer to the partial type.
        /// </summary>
        [Category( "Interfaces" )]
        [DisplayName( "IEquatable" )]
        public PartialType ImplementIEquatable( string[] equatablePropertyNames, Action<EquatableInterface> configure = null ) {
            // Mark all specified properties as equatable.
            var targets = this.Implementers.OfType<EquatableInterface.ITarget>( );
            EquatableInterface.ITarget[] equatableProperties;
            if( equatablePropertyNames == null ) {
                equatableProperties = targets.ToArray( );
            }
            else {
                equatableProperties = equatablePropertyNames
                    .Join( targets, name => name, prop => prop.Name, ( name, prop ) => prop, StringComparer.OrdinalIgnoreCase )
                    .ToArray( );

                // Ensure that all equatable properties were found.
                if( equatableProperties.Length != equatablePropertyNames.Length ) {
                    string[] missingNames =
                        equatablePropertyNames
                            .Where( name => !equatableProperties.Any( p => name.Equals( p.Name, StringComparison.OrdinalIgnoreCase ) ) )
                            .ToArray( );
                    Ensure.Satisfies( false, "Could not find equatable properties for all names: {0}", string.Join( ", ", missingNames ) );
                }
            }

            foreach( var property in equatableProperties ) {
                property.Equatable = true;
            }

            return this.AddImplementer( new EquatableInterface( this ), configure );
        }
        public PartialType ImplementIEquatable( params string[] equatablePropertyNames ) { return this.ImplementIEquatable( equatablePropertyNames, null ); }


        /// <summary>
        /// Adds a new <see cref="PropertyNameConstants"/> implementer to the partial type.
        /// </summary>
        [Category( "Misc" )]
        [DisplayName( "Property Name Constants" )]
        public PartialType ImplementPropertyNameConstants( Action<PropertyNameConstants> configure = null ) {
            return this.AddImplementer( new PropertyNameConstants( this ), configure );
        }


        private PartialType AddImplementer<T>( T implementer, Action<T> configure )
            where T : IImplementer {
            Ensure.NotNull( implementer );

            if( configure != null )
                configure( implementer );

            this.implementers_.Add( implementer );

            return this;
        }

    }

}
